﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace PSE.Framework.Threading
{
    /// <summary>
    /// Classe que executa delegates assincronamente e em fila
    /// </summary>
    public class ExecutionQueue
    {
        private Queue<WorkItem> _workItems = new Queue<WorkItem>();
        private bool _delegateQueuedOrRunning = false;

        /// <summary>
        /// Enfilera um delegate para ser executado
        /// </summary>
        /// <param name="callback">Delegate a ser executado</param>
        public void QueueUserWorkItem(WaitCallback callback)
        {
            this.QueueUserWorkItem(callback, null);
        }

        /// <summary>
        /// Enfilera um delegate para ser executado
        /// </summary>
        /// <param name="callback">Delegate a ser executado</param>
        /// <param name="state">Objeto com informações a ser passado para o delegate</param>
        public void QueueUserWorkItem(WaitCallback callback, object state)
        {
            if (callback == null)
                throw new ArgumentNullException("callback");

            var item = new WorkItem
            {
                Callback = callback,
                State = state,
                Context = ExecutionContext.Capture()
            };

            lock (_workItems)
            {
                _workItems.Enqueue(item);

                if (!_delegateQueuedOrRunning)
                {
                    _delegateQueuedOrRunning = true;
                    ThreadPool.UnsafeQueueUserWorkItem(ProcessQueuedItems, null);
                }
            }
        }

        private void ProcessQueuedItems(object ignored)
        {
            while (true)
            {
                WorkItem item;
                lock (_workItems)
                {
                    if (_workItems.Count == 0)
                    {
                        _delegateQueuedOrRunning = false;
                        break;
                    }
                    item = _workItems.Dequeue();
                }
                try
                {
                    item.Execute();
                }
                catch
                {
                    ThreadPool.UnsafeQueueUserWorkItem(ProcessQueuedItems, null);

                    throw;
                }
            }
        }
    }

    internal class WorkItem
    {
        public WaitCallback Callback;
        public object State;
        public ExecutionContext Context;

        private static ContextCallback _contextCallback = s =>
        {
            var item = (WorkItem)s;
            item.Callback(item.State);
        };

        public void Execute()
        {
            if (Context != null)
                ExecutionContext.Run(Context, _contextCallback, this);
            else
                Callback(State);
        }
    }
}
